// Copyright (c) 2009 Luca Marrocco.
// Modified or written by Luca Marrocco for inclusion with codej.
// Licensed under the Apache License, Version 2.0 (the "License")
package codej.gen.java;

import java.util.*;

import codej.ast.*;
import codej.gen.*;
import codej.gen.Token;
import codej.parser.java.*;
import codej.visit.java.*;

/** @author Luca Marrocco */
public class JavaProducer implements Visitor {

	private Tokens tokens = new Tokens();
	private Buffer buffer = new JavaBuffer();

	public JavaProducer() {
	}

	private void accept(Node node) {
		node.accept(this);
		append(token(" "));
	}

	private JavaProducer append(Token token) {
		buffer.append(token);
		return this;
	}

	protected void className(String name) {
		typeName(name, "class");
	}

	protected void importNormal(String name) {
		append(token("import"));
		append(token(" "));
		append(token(name));
		append(token(";"));
		append(token("\n"));
	}

	protected void importStatic(String name) {
		append(token("import"));
		append(token(" "));
		append(token("static"));
		append(token(" "));
		append(token(name));
		append(token(";"));
		append(token("\n"));
	}

	protected void interfaceName(String name) {
		typeName(name, "interface");
	}

	protected void method(String typeName, String name) {
		append(token(" "));
		append(token(typeName));
		append(token(" "));
		append(token(name));
	}

	protected void packageName(String packageName) {
		append(token("package"));
		append(token(" "));
		append(token(packageName));
		append(token(";"));
		append(token("\n"));
	}

	public void setTokens(Tokens tokens) {
		this.tokens = tokens;
	}

	private void todo(String something) {
		append(token("complete this" + something));
	}

	private Token token(String name) {
		return tokens.getToken(name);
	}

	public String toString() {
		return buffer.toString();
	}

	private void typeName(String name, String stereotype) {
		append(token(stereotype));
		append(token(" "));
		append(token(name));
		append(token(" "));
	}

	public void visit(Arguments arguments) {
		append(token("("));
		for (Node node : arguments) {
			node.accept(this);
		}
		append(token(")"));
	}

	public void visit(ArrayInitializer arrayInitializer) {
		for (int i = 0; i < arrayInitializer.getDimension(); i++)
			append(token("[")).append(token("]"));
	}

	public void visit(Assert statement) {
		todo("assert");
	}

	public void visit(AssignStatement assignStatement) {
		append(token(assignStatement.getIdentifier()));
		append(token("="));
		Expression expression = assignStatement.getExpression();
		if (expression != null)
			expression.accept(this);
	}

	public void visit(Block block) {
		append(token(" ")).append(token("{")).append(token(" "));

		for (Statement statement : block.filter(Statement.class))
			statement.accept(this);

		append(token(" ")).append(token("}")).append(token(" "));
	}

	public void visit(Bound bound) {
		todo("bound");
	}

	public void visit(Break Break) {
		todo("break");
	}

	public void visit(CastExpression castExpression) {

	}

	public void visit(ClassOrInterfaceTypeDeclaration typeDeclaration) {
		visitModifier(typeDeclaration.filter(Modifier.class));

		append(token(" "));

		boolean isInterface = typeDeclaration.isInterface();

		if (isInterface) {
			interfaceName(typeDeclaration.getName());
		} else {
			className(typeDeclaration.getName());
		}

		visitExtendsTypes(typeDeclaration.getExtendsTypes());
		visitImplementsTypes(typeDeclaration.getImplementsTypes());

		append(token("{ "));
		append(token("\n"));
		visitBodyDeclarations(typeDeclaration.getBodyDeclarations());
		append(token(" }"));
		append(token("\n"));
	}

	public void visit(CompilationUnit compilationUnit) {
		boolean packageNameIsDefined = compilationUnit.getPackageName() != null && !compilationUnit.getPackageName().equals("null");

		if (packageNameIsDefined) {
			packageName(compilationUnit.getPackageName());
		}

		for (ImportDeclaration importDeclaration : compilationUnit.filter(ImportDeclaration.class))
			importDeclaration.accept(this);

		for (TypeDeclaration typeDeclaration : compilationUnit.filter(ClassOrInterfaceTypeDeclaration.class))
			typeDeclaration.accept(this);

	}

	public void visit(Continue Continue) {
		todo("continue");
	}

	public void visit(Do doStatement) {
		append(token("do"));
		for (Statement statement : doStatement.filter(Statement.class))
			statement.accept(this);
		append(token("while"));
		for (Expression expression : doStatement.filter(Expression.class))
			expression.accept(this);
		append(token(";"));
	}

	public void visit(Documentation documentation) {
		todo("documentation");
	}

	public void visit(Empty Empty) {
		todo("empty");
	}

	public void visit(EnumDeclaration enumDeclaration) {
		todo("enumDeclaration");
	}

	public void visit(ExpressionInitializer expressionInitializer) {
		for (int i = 0; i < expressionInitializer.getDimension(); i++)
			append(token("[")).append(token("]"));

		if (expressionInitializer.getExpression() != null) {
			append(token(" "));
			append(token("="));
			append(token(" "));
			expressionInitializer.getExpression().accept(this);
		}
	}

	public void visit(Expressions expressions) {
		for (Node node : expressions) {
			node.accept(this);
		}
	}

	public void visit(ExpressionStatement expressionStatement) {
		for (Node node : expressionStatement) {
			node.accept(this);
		}
		append(token(";"));
	}

	public void visit(For For) {
		append(token("for"));
		append(token("("));
		For.getForControl().accept(this);
		append(token(")"));
		For.getStatement().accept(this);
	}

	public void visit(ForEach forEach) {
		todo("forEach");
	}

	public void visit(ForInit forInit) {
		forInit.getVariableDeclaration().accept(this);
	}

	public void visit(ForIteration forIterator) {
		forIterator.getForInit().accept(this);
		forIterator.getExpression().accept(this);
		append(token(";"));
		forIterator.getForUpdate().accept(this);
	}

	public void visit(FormalParameter formalParameter) {
		if (formalParameter.isFinal())
			append(token("final"));
		formalParameter.getType().accept(this);
		append(token(" "));
		append(token(formalParameter.getName()));
		for (int i = 0; i < formalParameter.getDimension(); i++)
			append(token("[")).append(token("]"));
	}

	public void visit(ForUpdate forUpdate) {
		for (Node node : forUpdate) {
			node.accept(this);
		}
	}

	public void visit(Id id) {
		append(token(id.getId()));
	}

	public void visit(IdExpression idExpression) {
		append(token(idExpression.getIdentifier()));
	}

	public void visit(Ids ids) {
		for (Node node : ids) {
			node.accept(this);
		}
	}

	public void visit(If If) {
		append(token("if"));
		append(token(" "));
		If.getExpression().accept(this);
		append(token(" "));
		If.getStatement().accept(this);
		if (If.hasElse()) {
			append(token(" "));
			append(token("else"));
			append(token(" "));
			If.getElseStatement().accept(this);
		}
	}

	public void visit(ImportDeclaration importDeclaration) {
		if (importDeclaration.isStatic()) {
			importStatic(importDeclaration.getName());
		} else {
			importNormal(importDeclaration.getName());
		}
	}

	public void visit(InstanceOfExpression instanceOfExpression) {
		instanceOfExpression.getType().accept(this);
	}

	public void visit(Literal literal) {
		append(token(literal.getValue()));
	}

	public void visit(LocalVariableDeclaration localVariableDeclaration) {
		if (localVariableDeclaration.getFinal())
			append(token("final"));
		append(token(" "));
		if (localVariableDeclaration.getType() != null)
			localVariableDeclaration.getType().accept(this);
		append(token(" "));
		for (VariableDeclarator variableDeclarator : localVariableDeclaration.filter(VariableDeclarator.class))
			variableDeclarator.accept(this);
		append(token(";"));
	}

	public void visit(MethodDeclaration methodDeclaration) {
		visitModifier(methodDeclaration.filter(Modifier.class));

		method(methodDeclaration.getType().getName(), methodDeclaration.getName());

		append(token("("));
		List<FormalParameter> formalParamters = methodDeclaration.getFormalParamters();
		Iterator<FormalParameter> formalParameterIterator = visitFormalParameters(formalParamters);
		append(token(")"));

		if (!methodDeclaration.getThrows().isEmpty()) {
			append(token(" "));
			append(token("throws"));
			append(token(" "));
			Iterator<String> throwsIterator = methodDeclaration.getThrows().iterator();
			while (throwsIterator.hasNext()) {
				String Throws = throwsIterator.next();
				append(token(Throws));
				if (formalParameterIterator.hasNext())
					append(token(", "));
			}
		}

		boolean hasBlock = methodDeclaration.getBlock() != null;
		if (hasBlock)
			methodDeclaration.getBlock().accept(this);
		if (!hasBlock)
			append(token(";"));
	}

	public void visit(Modifier modifier) {
		append(token(modifier.getName()));
	}

	public void visit(NextExpression nextExpression) {
		append(token(" "));
		append(token(nextExpression.getOperator()));
		append(token(" "));
		nextExpression.getNextExpression().accept(this);
	}

	public void visit(NextId nextId) {
		append(token(nextId.getOp()));
		append(token(nextId.getNextId()));
	}

	public void visit(NextVariableDeclaration nextVariableDeclaration) {
		todo("nextVariableDecl");
	}

	public void visit(NextVariableDeclarator nextVariableDeclarator) {
		append(token(nextVariableDeclarator.getOperator()));
		append(token(" "));
		nextVariableDeclarator.getNextVariableDeclarator().accept(this);
	}

	public void visit(NumberExpression numExpression) {
		append(token(numExpression.getNum().toPlainString()));
	}

	public void visit(OperationExpression operationExpression) {
		Expression expression1 = operationExpression.getExpression1();
		Expression expression2 = operationExpression.getExpression2();
		ExpressionOperator operator = operationExpression.getOperator();

		if (expression1 != null)
			expression1.accept(this);
		if (operator != null)
			append(token(operator.toString()));
		if (expression2 != null)
			expression2.accept(this);
	}

	public void visit(ParExpression parExpression) {
		append(token("("));
		parExpression.getExpression().accept(this);
		append(token(")"));
	}

	public void visit(Primary primary) {
		for (Node node : primary) {
			node.accept(this);
		}
	}

	public void visit(PrintStatement printStatement) {
		append(token("print"));
		append(token("("));
		Expression expression = printStatement.getExpression();
		if (expression != null)
			expression.accept(this);
		append(token(")"));
	}

	public void visit(Return statement) {
		append(token("return"));
		append(token(" "));
		statement.getExpression().accept(this);
		append(token(";"));
	}

	public void visit(Selector selector) {
		todo("selector");
	}

	public void visit(Statements statements) {
		for (Node node : statements) {
			node.accept(this);
		}
	}

	public void visit(Switch statement) {
		append(token("switch"));
		statement.getExpression().accept(this);
		append(token(" "));
		append(token("{"));
		append(token(" "));
		for (Node node : statement) {
			node.accept(this);
		}
		append(token(" "));
		append(token("}"));
	}

	public void visit(SwitchConstant switchConstant) {
		append(token("case"));
		append(token(":"));
		append(token(" "));
		for (Node node : switchConstant) {
			node.accept(this);
		}
	}

	public void visit(SwitchDefault switchDefault) {
		append(token("default"));
		append(token(":"));
		append(token(" "));
		for (Node node : switchDefault) {
			node.accept(this);
		}
	}

	public void visit(SwitchExpression switchExpression) {
		append(token("case"));
		append(token(" "));
		switchExpression.getExpression().accept(this);
		append(token(":"));
		append(token(" "));
		for (Node node : switchExpression) {
			node.accept(this);
		}
	}

	public void visit(Synchronized Synchronized) {
		todo("synchronized");
	}

	public void visit(Throw Throw) {
		todo("throw");
	}

	public void visit(Token token) {
		todo("token");
	}

	public void visit(Try Try) {
		todo("try");
	}

	public void visit(Type type) {
		append(token(type.getName()));
		for (int i = 0; i < type.getDimension(); i++)
			append(token("[")).append(token("]"));
	}

	public void visit(TypeDeclarationStatement typeDeclarationStatement) {
		todo("typeDeclarationStatement");
	}

	public void visit(TypeParameter typeParameter) {
		todo("typeParameter");
	}

	public void visit(UnaryExpression unaryExpression) {
		if (unaryExpression.hasSign()) {
			append(token(unaryExpression.getSign()));
			unaryExpression.getExpression().accept(this);
		} else if (unaryExpression.hasPrimary()) {
			unaryExpression.getPrimary().accept(this);
			for (Selector selector : unaryExpression.getSelectors()) {
				selector.accept(this);
			}
			unaryExpression.getExpression().accept(this);
			append(token(unaryExpression.getPlusOrMinus()));
		}
	}

	public void visit(VariableDeclaration variableDeclaration) {
		for (Modifier modifier : variableDeclaration.filter(Modifier.class)) {
			accept(modifier);
		}

		if (variableDeclaration.getType() != null)
			append(token(variableDeclaration.getType().getName()));

		variableDeclaration.getVariableDeclarators().accept(this);
		append(token(";"));
	}

	public void visit(VariableDeclarator variableDeclarator) {
		for (int i = 0; i < variableDeclarator.getDimension(); i++)
			append(token("[")).append(token("]"));

		append(token(" "));
		append(token(variableDeclarator.getName()));

		VariableInitializer variableInitializer = variableDeclarator.getVariableInitializer();
		if (variableInitializer == null)
			return;
		variableInitializer.accept(this);
	}

	public void visit(VariableDeclarators variableDeclarators) {
		for (Node node : variableDeclarators) {
			node.accept(this);
		}
	}

	public void visit(VariableInitializer variableInitializer) {
		todo("variableInitializer");
	}

	public void visit(While whileStatement) {
		append(token("while"));
		for (Expression expression : whileStatement.filter(Expression.class))
			expression.accept(this);
		for (Statement statement : whileStatement.filter(Statement.class))
			statement.accept(this);
	}

	private void visitBodyDeclarations(List<BodyDeclaration> bodyDeclarations) {
		for (BodyDeclaration bodyDeclaration : bodyDeclarations)
			bodyDeclaration.accept(this);
	}

	private void visitExtendsTypes(List<Type> extendsTypes) {
		if (!extendsTypes.isEmpty()) {
			append(token("extends"));
			for (Type extendsType : extendsTypes)
				extendsType.accept(this);
		}
	}

	private Iterator<FormalParameter> visitFormalParameters(List<FormalParameter> formalParamters) {
		Iterator<FormalParameter> formalParameterIterator = formalParamters.iterator();
		while (formalParameterIterator.hasNext()) {
			FormalParameter formalParameter = formalParameterIterator.next();
			formalParameter.accept(this);
			if (formalParameterIterator.hasNext())
				append(token(","));
		}
		return formalParameterIterator;
	}

	private void visitImplementsTypes(List<Type> implementsTypes) {
		if (!implementsTypes.isEmpty()) {
			append(token("implements"));
			for (Type implementsType : implementsTypes)
				implementsType.accept(this);
		}
	}

	private void visitModifier(Iterable<Modifier> modifiers) {
		for (Modifier modifier : modifiers) {
			accept(modifier);
		}
	}
	
	public void visit(Annotations annotations) {
		new AnnotationsVisitor().visit(annotations, this);
	}
}